#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <math.h>
#include "uniform.h"
#include "random.h"

void UniformReal_Init_Params(UniformReal_t * dist, int nparams, ...)
{
  if (2 == nparams)
  {
    va_list ap;
  
    va_start(ap, nparams);

    dist->low = va_arg(ap, double);
    dist->high = va_arg(ap, double);
    dist->fixed_low_high = 1;

    va_end(ap);

    if (dist->high <= dist->low)
    {
      fprintf(stderr, "error: UniformReal requires low < high\n");
      exit(1);
    }
  }
  else if (0 == nparams)
  {
    dist->fixed_low_high = 0;
  }
  else
  {
    fprintf(stderr, "error: UniformReal takes at most 1 parameter\n");
    exit(1);
  }
}


double UniformReal_Sample(const UniformReal_t * dist, int nargs, ...)
{
  double low, high;
  
  if (dist->fixed_low_high)
  {
    low = dist->low;
    high = dist->high;
  }

  else if (2 == nargs)
  {
    va_list ap;
  
    va_start(ap, nargs);
    
    low = va_arg(ap, double);
    high = va_arg(ap, double);

    if (high <= low)
    {
      fprintf(stderr, "error: UniformReal requires low < high\n");
      exit(1);
    }
    
    va_end(ap);
  }
  else
  {
    fprintf(stderr, "error: UniformReal requires 2 arguments (low, high)\n");
    exit(1);
  }

  return low + (high - low) * RANDOM_DOUBLE;
}


double UniformReal_LogProb(const UniformReal_t * dist, double val, 
                           int nargs, ...)
{
  double low, high;
  
  if (dist->fixed_low_high)
  {
    low = dist->low;
    high = dist->high;
  }

  else if (2 == nargs)
  {
    va_list ap;
  
    va_start(ap, nargs);
    
    low = va_arg(ap, double);
    high = va_arg(ap, double);
    
    if (high <= low)
    {
      fprintf(stderr, "error: UniformReal requires low < high\n");
      exit(1);
    }
    
    va_end(ap);
  }
  else
  {
    fprintf(stderr, "error: UniformReal requires 2 arguments (low, high)\n");
    exit(1);
  }

  if ((val <= low) || (val >= high))
    return -HUGE_VAL;
  
  return - log(high - low);
}

double UniformReal_Prob(const UniformReal_t * dist, double val, 
                        int nargs, ...)
{
  double low, high;
  
  if (dist->fixed_low_high)
  {
    low = dist->low;
    high = dist->high;
  }

  else if (2 == nargs)
  {
    va_list ap;
  
    va_start(ap, nargs);
    
    low = va_arg(ap, double);
    high = va_arg(ap, double);
    
    if (high <= low)
    {
      fprintf(stderr, "error: UniformReal requires low < high\n");
      exit(1);
    }
    
    va_end(ap);
  }
  else
  {
    fprintf(stderr, "error: UniformReal requires 2 arguments (low, high)\n");
    exit(1);
  }

  if ((val <= low) || (val >= high))
    return 0;
  
  return (double) 1.0 / (high - low);
}

void UniformChoice_Init_Params(UniformChoice_t * dist, int nparams)
{
  if (nparams != 0)
  {
    fprintf(stderr, "error: UniformChoice takes no parameters\n");
    exit(1);
  }
}



int UniformChoice_Sample(const UniformChoice_t * dist, int nargs, 
                         int num_items)
{
  if (nargs != 1)
  {
    fprintf(stderr, "error: UniformChoice takes exactly one argument\n");
    exit(1);
  }

  if (!num_items)
    return -1;
  
  return (int) floor(RANDOM_DOUBLE * ((double) num_items));
}


double UniformChoice_LogProb(const UniformChoice_t * dist, int val,
                             int nargs, int num_items)
{
  if (nargs != 1)
  {
    fprintf(stderr, "error: UniformChoice takes exactly one argument\n");
    exit(1);
  }

  if (!num_items)
  {
    if (-1 == val)
      return 0.0;
    
    else
      return -HUGE_VAL;
  }
  
  return -log ((double) num_items);
}

double UniformChoice_Prob(const UniformChoice_t * dist, int val,
                          int nargs, int num_items)
{
  if (nargs != 1)
  {
    fprintf(stderr, "error: UniformChoice takes exactly one argument\n");
    exit(1);
  }

  if (!num_items)
  {
    if (-1 == val)
      return 1.0;
    
    else
      return 0.0;
  }
  
  return ((double) 1) / ((double) num_items);
}


